/*

Copyright 2009 Nicola Dal Pont

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Version ${MAJOR_VERSION}.${MINOR_VERSION}${BETA_MARK} Build ${BUILD_NUMBER}

*/

package com.astoolkit.workflow.parsley
{
	import com.astoolkit.commons.factory.IPooledFactoryDelegate;
	import com.astoolkit.commons.factory.PooledFactory;
	import com.astoolkit.workflow.BaseTask;
	
	import flash.events.Event;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	import mx.core.IFactory;
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.rpc.events.FaultEvent;
	import mx.utils.ObjectUtil;
	import mx.utils.StringUtil;
	
	import org.spicefactory.parsley.core.messaging.command.CommandObserverProcessor;
	import org.spicefactory.parsley.core.messaging.command.CommandStatus;
	import org.spicefactory.parsley.core.messaging.receiver.CommandObserver;
	import org.spicefactory.parsley.core.scope.Scope;
	import org.spicefactory.parsley.core.scope.ScopeName;
	
	/**
	 * Sends a message through Parsley's message bus.<br><br>
	 * If isCommand is set to <code>true</code>, the task will
	 * wait for either a command result or a fault to complete.
	 * Otherwise it will complete straight after sending the message. 
	 */
	[Bindable]
	public class SendParsleyMessage extends BaseTask implements IParsleyCommandObserverTask
	{
		
		private static const LOGGER : ILogger = 
			Log.getLogger( getQualifiedClassName( SendParsleyMessage ).replace(/:+/g, "." ) );
		
		private var _processor : CommandObserverProcessor;
		
		private var _result : *;
		
		/**
		 * the message instance to be sent.<br><br>
		 * If omitted, the task will try send the data in the pipeline as message.
		 * If an instance of <code>mx.core.IFactory</code> is passed, the message will be
		 * instantiated using its newInstance() method.
		 */
		public var message : Object;
		

		/**
		 * the message handler selector value
		 */
		public var selector : Object;
		
		/**
		 * the message bus scope
		 */
        public var scope : String = ScopeName.GLOBAL;
		/**
		 * an optional object which name-value pairs represents the
		 * mapping between the properties in the pipeline data object
		 * and the message to be sent where name is the message's property name
		 * and value is the pipeline data object property name to be copied.
		 * <br><br>
		 * 
		 * <br><br>
		 * @example Mapping message properties from pipeline data<br>
		 * <listing>
		 * &lt;SendParsleyMessage
		 * 		messagePropertiesMapping="{ { itemId : 'id', price : 'priceWithVAT' } }"
		 * 		message="{ new ClassFactory( SendItemPriceMessage ) }" 
		 * 		/&gt;
		 * </listing>
		 * 
		 * If taskInputFilter is set, mapping is performed on the filtered data.
		 */
		public var messagePropertiesMapping : Object;
		
		[Inspectable(enumeration="fail,ignore,log-error,log-warn,log-info,log-debug", defaultValue="fail")]
		public var messageMappingFailurePolicy : String = "fail";
		
		/**
		 * if true this task will add a responder to the message's destination
		 * and will wait for a result or a fault event to be dispatched
		 * before completing.<br><br>
		 * 
		 * In Parsley terms, we use <code>isCommand="true"</code>
		 * to invoke a <code>[Command]</code> tagged function returning an AsyncToken.
		 * Otherwise, for a <code>[MessageHandler]</code> tagged function we set
		 *  <code>isCommand="false"</code>.
		 */
		public var isCommand : Boolean = false;
		
		private var _errorObserver : MessageObserver;
		private var _completeObserver : MessageObserver;
		private var _parsleyHelper : ParsleyMessageBusHelper;
		
		override public function initialize():void
		{
			_parsleyHelper = getParsleyHelperFromContext();
			if( !_parsleyHelper ) 
			{
				fail( getQualifiedClassName( this ) + " cannot be ran because the provided " +
					"implementation of IWorkflowContext doesn't provide access to an instance of " +
					"ParsleyMessageBusHelper" );
				return;
			}
		}
		
		override public function prepare():void
		{
			super.prepare();
			_result = null;
		}
		
		public function get errorObserver() : MessageObserver 
		{
			return _errorObserver;
		}
		
		public function get completeObserver() : MessageObserver 
		{
			return _completeObserver;
		}
		
		private function onObserverCommandComplete( inEvent : Event ) : void
		{
			if( isCommand )
			{
				removeSuspendableListener( _completeObserver, Event.COMPLETE, onObserverCommandComplete );
				removeSuspendableListener( _errorObserver, FaultEvent.FAULT, onObserverCommandFault );
			}
			_result = _completeObserver.result;
			_pipelineData = _result;
			complete();
		}
		
		private function onObserverCommandFault( inEvent : FaultEvent ) : void
		{
			if( isCommand )
			{
				removeSuspendableListener( _completeObserver, Event.COMPLETE, onObserverCommandComplete );
				removeSuspendableListener( _errorObserver, FaultEvent.FAULT, onObserverCommandFault );
			}
			fail( inEvent.message as String );
		}
		
		
		
		override public function cleanUp():void
		{
			super.cleanUp();
			_result = null;
			if( isCommand )
			{
				if( _parsleyHelper)
					_parsleyHelper.unwireAsyncCommandTask( this );
				_completeObserver = null;
				_errorObserver = null;
			}
		}

		override public function begin():void
		{
			super.begin();
			var aMessage : Object = message;
			var pData : Object = filteredPipelineData;
			if( !aMessage )
			{
				aMessage = pData;
			}
			else 
			{
				if( aMessage is IFactory )
				{
					aMessage = IFactory( aMessage ).newInstance();
				}
				if( messagePropertiesMapping )
				{
					for( var k : String in messagePropertiesMapping )
					{
						if( aMessage.hasOwnProperty( k ) && 
							( pData.hasOwnProperty( messagePropertiesMapping[ k ] ) || 
								messagePropertiesMapping[ k ] == "." ) )
						{
							if( messagePropertiesMapping[ k ] == "." )
								aMessage[ k ] = pData;
							else
								aMessage[ k ] = pData[ messagePropertiesMapping[ k ] ];
						}
						else
						{
							if( messageMappingFailurePolicy == "ignore" )
							{
								continue;
							}
							else
							{
								var errorMessage : String = 
									"Failed mapping pipeline data to message: " +
										"{0} has no property '{1}'";
								if( !aMessage.hasOwnProperty( k ) )
									errorMessage = StringUtil.substitute( 
										errorMessage, 
										getQualifiedClassName( aMessage ), k );
								else
									errorMessage = StringUtil.substitute( 
										errorMessage,
										getQualifiedClassName( pData ),
										messagePropertiesMapping[ k ] );
								if( messageMappingFailurePolicy == "fail" )
								{
									fail( errorMessage );  
									return;
								}
								else if( messageMappingFailurePolicy.match( /^log\-/ ) )
									LOGGER[ messageMappingFailurePolicy.replace( /^log\-/, "" ) ]( errorMessage );
							}
							
						}
					}

				}
			}
			if( isCommand )
			{
				
				var clazz : Class = getDefinitionByName( getQualifiedClassName( message ) ) as Class;
				_completeObserver = new MessageObserver( clazz, CommandStatus.COMPLETE );
				_errorObserver = new MessageObserver( clazz, CommandStatus.ERROR );
				_completeObserver.selector = selector;
				_errorObserver.selector = selector;
				_parsleyHelper.wireAsyncCommandTask( this, clazz );
				addSuspendableEventListener( _completeObserver, Event.COMPLETE, onObserverCommandComplete ); 
				addSuspendableEventListener( _errorObserver, FaultEvent.FAULT, onObserverCommandFault );
			}
			_parsleyHelper.context.scopeManager.getScope( scope ).dispatchMessage( message, selector );
			if( !isCommand )
				complete();
		}
			
		
		/**
		 * if <code>isCommand="true"</code>, <code>result</code> will contain 
		 * the data coming from the async operation.
		 */
		public function get result() : *
		{
			return _result;
		}
		
		
		private function getParsleyHelperFromContext() : ParsleyMessageBusHelper
		{
			for( var k : String in _context )
			{
				if( _context[ k ] is ParsleyMessageBusHelper )
					return _context[ k ];
			}
			return null;
		}
	}
}

